package com.wzh.common.general.log;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.alibaba.fastjson2.JSON;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * 操作日志切面处理
 *
 * @author wzh
 */
@Slf4j
@Aspect
@Component
public class LogAspect {

    /**
     * 排除敏感属性字段
     */
    private static final String[] EXCLUDE_PROPERTIES = {"password", "oldPassword", "newPassword", "confirmPassword"};

    /**
     * 计算操作消耗时间
     */
    private static final ThreadLocal<Long> TIME_THREADLOCAL = new ThreadLocal<>();

    @Before("@annotation(logAnnotation)")
    public void doBefore(JoinPoint joinPoint, Log logAnnotation) {
        TIME_THREADLOCAL.set(System.currentTimeMillis());
    }

    @AfterReturning(pointcut = "@annotation(logAnnotation)", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, Log logAnnotation, Object jsonResult) {
        handleLog(joinPoint, logAnnotation, null, jsonResult);
    }

    @AfterThrowing(value = "@annotation(logAnnotation)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Log logAnnotation, Exception e) {
        handleLog(joinPoint, logAnnotation, e, null);
    }

    protected void handleLog(final JoinPoint joinPoint, Log logAnnotation, final Exception e, Object jsonResult) {
        try {
            RequestAttributes ra = RequestContextHolder.getRequestAttributes();
            ServletRequestAttributes sra = (ServletRequestAttributes) ra;
            if (sra == null) {
                return;
            }
            HttpServletRequest request = sra.getRequest();

            // 获取当前的用户（这里需要根据实际的用户认证方式来获取）
            String username = "unknown";
            if (StpUtil.isLogin()) {
                username = StpUtil.getLoginId().toString();
            }
            // 日志实体对象（这里可以根据实际需求来存储日志，比如保存到数据库）
            Map<String, Object> operationLog = new HashMap<>();
            operationLog.put("status", e == null ? 1 : 0);
            operationLog.put("username", username);

            // 请求的地址
            String ip = getIpAddress(request);
            operationLog.put("operationIp", ip);
            operationLog.put("requestUrl", request.getRequestURI());

            if (e != null) {
                operationLog.put("errorMsg", CharSequenceUtil.sub(e.getMessage(), 0, 2000));
            }

            // 设置方法名称
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            operationLog.put("method", className + "." + methodName + "()");

            // 设置请求方式
            operationLog.put("requestMethod", request.getMethod());

            // 处理设置注解上的参数
            getControllerMethodDescription(joinPoint, logAnnotation, operationLog, request);

            // 设置消耗时间
            operationLog.put("costTime", System.currentTimeMillis() - TIME_THREADLOCAL.get());

            // 处理完成后清理
            TIME_THREADLOCAL.remove();

            // 保存日志（这里只是打印，实际项目中可以存储到数据库）
            log.info("操作日志: {}", JSON.toJSONString(operationLog));
        } catch (Exception exp) {
            log.error("记录操作日志异常:", exp);
        }
    }

    /**
     * 获取注解中对方法的描述信息
     */
    private void getControllerMethodDescription(JoinPoint joinPoint, Log log, Map<String, Object> operationLog,
                                                HttpServletRequest request) {
        operationLog.put("module", log.module());
        operationLog.put("type", log.type().name());
        operationLog.put("description", log.description());

        // 是否需要保存request，参数和值
        if (log.saveRequestData()) {
            setRequestValue(joinPoint, operationLog, request);
        }
        // 是否需要保存response，参数和值
        if (log.saveResponseData() && operationLog.get("jsonResult") != null) {
            operationLog.put("jsonResult", CharSequenceUtil.sub(JSON.toJSONString(operationLog.get("jsonResult")), 0, 2000));
        }
    }

    /**
     * 获取请求的参数，放到log中
     */
    private void setRequestValue(JoinPoint joinPoint, Map<String, Object> operationLog, HttpServletRequest request) {
        String requestMethod = operationLog.get("requestMethod").toString();
        if ("POST".equals(requestMethod) || "PUT".equals(requestMethod)) {
            String params = argsArrayToString(joinPoint.getArgs());
            operationLog.put("requestParam", CharSequenceUtil.sub(params, 0, 2000));
        } else {
            Map<?, ?> paramsMap = request.getParameterMap();
            if (!paramsMap.isEmpty()) {
                operationLog.put("requestParam", CharSequenceUtil.sub(JSON.toJSONString(paramsMap), 0, 2000));
            }
        }
    }

    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray) {
        StringBuilder params = new StringBuilder();
        if (paramsArray != null) {
            for (Object o : paramsArray) {
                if (o != null && !isFilterObject(o)) {
                    try {
                        String jsonObj = JSON.toJSONString(o);
                        params.append(jsonObj).append(" ");
                    } catch (Exception e) {
                        log.error(e.getMessage(), e);
                    }
                }
            }
        }
        return params.toString().trim();
    }

    /**
     * 判断是否需要过滤的对象
     */
    private boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection<?> collection = (Collection<?>) o;
            for (Object value : collection) {
                return value instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map<?, ?> map = (Map<?, ?>) o;
            for (Object value : map.values()) {
                return value instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletRequest
                || o instanceof BindingResult;
    }

    /**
     * 获取IP地址
     */
    private String getIpAddress(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Forwarded-For");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
    }
}